/*-------------------------------------------------------------------------
 *
 * ginlogic.c
 *	  执行二元和三元逻辑一致性检查的例程。
 *
 * GIN 操作类可以提供布尔或三元一致
 * 函数，或者两者都有。此文件为 GIN 代码的其余部分提供布尔和三元
 * 接口，即使这些接口中只有一个是由操作类实现的。
 *
 * 当操作类只实现三元函数时提供布尔接口比较简单 - 只需按原样调用三元函数，
 * 并将 GIN_TRUE，GIN_FALSE，GIN_MAYBE
 * 返回代码映射到 TRUE，FALSE 和 TRUE+recheck。
 * 当操作类只实现布尔函数时提供三元接口则是通过多次调用布尔函数来实现的，
 * 所有 MAYBE 参数都设置为 TRUE 和 FALSE 的所有组合（最多一定数量的 MAYBE 参数）。
 *
 * （布尔函数足以确定一个项目是否匹配，但如果能确定一个项目匹配或不匹配，
 * 即使不知道某些键是否存在，GIN 扫描也可以应用各种优化。这就是三元一致性函数的用途。）
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginlogic.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/reloptions.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"


/*
 * shimTriConsistentFn 尝试通过调用所有组合来解析的最大 MAYBE 输入数量。
 */
#define MAX_MAYBE_ENTRIES	4

/*
 * 针对一个 EVERYTHING 键的虚拟一致性函数。仅声称它匹配。
 */
static bool fc_trueConsistentFn(GinScanKey fc_key)
{
	fc_key->recheckCurItem = false;
	return true;
}
static GinTernaryValue fc_trueTriConsistentFn(GinScanKey fc_key)
{
	return GIN_TRUE;
}

/*
 * 调用常规二元逻辑一致性函数的辅助函数。
 */
static bool fc_directBoolConsistentFn(GinScanKey fc_key)
{
	/*
	 * 初始化 recheckCurItem 以防 consistentFn 不知道它应该设置它。
	 * 在这种情况下，安全的假设是强制重新检查。
	 */
	fc_key->recheckCurItem = true;

	return DatumGetBool(FunctionCall8Coll(fc_key->consistentFmgrInfo,
										  fc_key->collation,
										  PointerGetDatum(fc_key->entryRes),
										  UInt16GetDatum(fc_key->strategy),
										  fc_key->query,
										  UInt32GetDatum(fc_key->nuserentries),
										  PointerGetDatum(fc_key->extra_data),
										  PointerGetDatum(&fc_key->recheckCurItem),
										  PointerGetDatum(fc_key->queryValues),
										  PointerGetDatum(fc_key->queryCategories)));
}

/*
 * 调用原生三元逻辑一致性函数的辅助函数。
 */
static GinTernaryValue fc_directTriConsistentFn(GinScanKey fc_key)
{
	return DatumGetGinTernaryValue(FunctionCall7Coll(fc_key->triConsistentFmgrInfo,
													 fc_key->collation,
													 PointerGetDatum(fc_key->entryRes),
													 UInt16GetDatum(fc_key->strategy),
													 fc_key->query,
													 UInt32GetDatum(fc_key->nuserentries),
													 PointerGetDatum(fc_key->extra_data),
													 PointerGetDatum(fc_key->queryValues),
													 PointerGetDatum(fc_key->queryCategories)));
}

/*
 * 该函数实现了一个二元逻辑一致性检查，使用 opclass 提供的三元逻辑一致性函数。
 * GIN_MAYBE 的返回值被解释为带有重新检查标志的真值。
 */
static bool fc_shimBoolConsistentFn(GinScanKey fc_key)
{
	GinTernaryValue fc_result;

	fc_result = DatumGetGinTernaryValue(FunctionCall7Coll(fc_key->triConsistentFmgrInfo,
													   fc_key->collation,
													   PointerGetDatum(fc_key->entryRes),
													   UInt16GetDatum(fc_key->strategy),
													   fc_key->query,
													   UInt32GetDatum(fc_key->nuserentries),
													   PointerGetDatum(fc_key->extra_data),
													   PointerGetDatum(fc_key->queryValues),
													   PointerGetDatum(fc_key->queryCategories)));
	if (fc_result == GIN_MAYBE)
	{
		fc_key->recheckCurItem = true;
		return true;
	}
	else
	{
		fc_key->recheckCurItem = false;
		return fc_result;
	}
}

/*
 * 该函数实现了一个三态一致性检查，使用 opclass 提供的布尔一致性函数。
 *
 * 我们的策略是用 TRUE/FALSE 的每种组合替换 MAYBE 输入来调用 consistentFn。
 * 如果 consistentFn 对每种组合返回相同的值，那就是总体结果。否则，返回 MAYBE。
 * 测试每种组合的时间复杂度是 O(n^2)，所以这仅适用于少量 MAYBE 输入。
 *
 * 注意：此函数修改了 key->entryRes 数组！
 */
static GinTernaryValue fc_shimTriConsistentFn(GinScanKey fc_key)
{
	int			fc_nmaybe;
	int			fc_maybeEntries[MAX_MAYBE_ENTRIES];
	int			fc_i;
	bool		fc_boolResult;
	bool		fc_recheck = false;
	GinTernaryValue fc_curResult;

	/*
	 * 计算有多少个 MAYBE 输入，并将它们的索引存储在 maybeEntries 中。
	 * 如果 MAYBE 输入过多，测试所有组合就不现实，所以放弃并返回 MAYBE。
	 */
	fc_nmaybe = 0;
	for (fc_i = 0; fc_i < fc_key->nentries; fc_i++)
	{
		if (fc_key->entryRes[fc_i] == GIN_MAYBE)
		{
			if (fc_nmaybe >= MAX_MAYBE_ENTRIES)
				return GIN_MAYBE;
			fc_maybeEntries[fc_nmaybe++] = fc_i;
		}
	}

	/*
	 * 如果输入都不是 MAYBE，那么我们可以直接调用一致性函数。
	 */
	if (fc_nmaybe == 0)
		return fc_directBoolConsistentFn(fc_key);

	/* 首先调用一致性函数，将所有 MAYBE 输入设置为 FALSE */
	for (fc_i = 0; fc_i < fc_nmaybe; fc_i++)
		fc_key->entryRes[fc_maybeEntries[fc_i]] = GIN_FALSE;
	fc_curResult = fc_directBoolConsistentFn(fc_key);

	for (;;)
	{
		/* 调整条目以进行下一个组合。 */
		for (fc_i = 0; fc_i < fc_nmaybe; fc_i++)
		{
			if (fc_key->entryRes[fc_maybeEntries[fc_i]] == GIN_FALSE)
			{
				fc_key->entryRes[fc_maybeEntries[fc_i]] = GIN_TRUE;
				break;
			}
			else
				fc_key->entryRes[fc_maybeEntries[fc_i]] = GIN_FALSE;
		}
		if (fc_i == fc_nmaybe)
			break;

		fc_boolResult = fc_directBoolConsistentFn(fc_key);
		fc_recheck |= fc_key->recheckCurItem;

		if (fc_curResult != fc_boolResult)
			return GIN_MAYBE;
	}

	/* 带有重新检查的 TRUE 被视为 MAYBE */
	if (fc_curResult == GIN_TRUE && fc_recheck)
		fc_curResult = GIN_MAYBE;

	return fc_curResult;
}

/*
 * 为扫描键设置一致性函数的实现。
 */
void ginInitConsistentFunction(GinState *fc_ginstate, GinScanKey fc_key)
{
	if (fc_key->searchMode == GIN_SEARCH_MODE_EVERYTHING)
	{
		fc_key->boolConsistentFn = fc_trueConsistentFn;
		fc_key->triConsistentFn = fc_trueTriConsistentFn;
	}
	else
	{
		fc_key->consistentFmgrInfo = &fc_ginstate->consistentFn[fc_key->attnum - 1];
		fc_key->triConsistentFmgrInfo = &fc_ginstate->triConsistentFn[fc_key->attnum - 1];
		fc_key->collation = fc_ginstate->supportCollation[fc_key->attnum - 1];

		if (OidIsValid(fc_ginstate->consistentFn[fc_key->attnum - 1].fn_oid))
			fc_key->boolConsistentFn = fc_directBoolConsistentFn;
		else
			fc_key->boolConsistentFn = fc_shimBoolConsistentFn;

		if (OidIsValid(fc_ginstate->triConsistentFn[fc_key->attnum - 1].fn_oid))
			fc_key->triConsistentFn = fc_directTriConsistentFn;
		else
			fc_key->triConsistentFn = fc_shimTriConsistentFn;
	}
}
